/*
 * @ModuleName: Http Request
 * @Author: yuetchn@163.com
 * @LastEditTime: 2023-01-13 15:47:16
 */

import axios, { AxiosRequestConfig, AxiosError } from "axios";
import { message, MessageArgsProps } from "ant-design-vue";
import { IAxiosResponseBody, AllStates } from "@/types";
import { GetToken } from "@/utils/cookie";
import store from "@/store";

interface IRequestFailedRetry {
  isOk: boolean;
  retryCount: number;
  result: () => Promise<any>;
}

const timeout = 1000 * import.meta.env.VITE_HTTP_REQUEST_TIME_OUT;
const failedRetryIntervalTime = import.meta.env.VITE_GLOBAL_REQUEST_FAILED_RETRY_INTERVAL_TIME;
const failedRetryCount = import.meta.env.VITE_GLOBAL_REQUEST_FAILED_RETRY_COUNT;

const req = axios.create({
  baseURL: import.meta.env.VITE_BASE_HOST,
  timeout,
  timeoutErrorMessage: "连接超时",
  failedRetryCount: import.meta.env.VITE_GLOBAL_REQUEST_FAILED_RETRY_COUNT,
});

// 请求队列
let queue = 0;
const msg_key = "request_queue_message_key";
const ShowLoading = ((info = "加载中") => {
  let time: any = 0;
  return () => {
    const nowTime = new Date().getTime();
    queue++;
    if (nowTime - time > 200) {
      time = nowTime;
      store.dispatch("AppModule/set_loadingShade", true);
      message.loading({
        content: info,
        duration: timeout,
        key: msg_key,
      });
    }
  };
})();

const HiddenLoading = (isForce = false) => {
  isForce ? (queue = 0) : queue--;
  if (queue > 0) {
    return;
  }
  queue = 0;
  store.dispatch("AppModule/set_loadingShade", false);
  message.destroy(msg_key);
};

req.interceptors.request.use(
  (config) => {
    // loading
    (config.loading !== false && !config.__timestamp) ? ShowLoading() : null;
    
    // headers
    const headers: Record<string, string> = {
      "Accept-Language": (store.state as any).AppModule.locale,
    };

    if (GetToken()) {
      headers.Authorization = GetToken() as string;
    }

    // custom params
    config.params = Object.assign(config.params || {}, {
      timestamp: new Date().getTime(),
    });

    config.headers = { ...config.headers, ...headers };
    return config;
  },
  (err) => {
    HiddenLoading(true)
    return Promise.reject(err);
  },
);

req.interceptors.response.use(
  (response) => {
    HiddenLoading();

    return response;
  },
  async (err: AxiosError<IAxiosResponseBody<any>>) => {
    const errMsg = {
      config: {
        content: "连接失败",
      } as MessageArgsProps,
      isFetry: false,
      isLoading: true,
      isRefreshToken: false,
      failed: undefined as IRequestFailedRetry | undefined,
      setErrMsg(content:string, type:"info" | "success" | "error" | "warning" | "loading") {
        this.config.content = content;
        this.config.type = type;
      },
      // 失败重试
      fetry() {
        this.failed = requestFailedRetry(err);
        this.isFetry = true;
      },
    };

    if (err.message.includes("timeout")) {
      errMsg.setErrMsg("连接超时", "error")
      errMsg.fetry();
    } else {
      switch (err.response?.status) {
        case 401:
          try {
            if ((store.state as AllStates).UserModule.isRefreshToken) {
              errMsg.isLoading = false;
              await store.dispatch("UserModule/resetRefreshToken");
              throw new Error("会话超时")
            }
            await store.dispatch("UserModule/refreshToken");
            errMsg.isRefreshToken = true;
          } catch (err) {
            errMsg.setErrMsg("会话超时", "warning");
            setTimeout(() => {
              store.dispatch("UserModule/loginOut");
            }, 1000);
          }
          
          break;
        case 502:
          errMsg.setErrMsg("网关错误", "error");
          errMsg.fetry();
          break;

        default:
          errMsg.setErrMsg(err.response?.data.msg || "连接失败", "error");
          break;
      }
    }

    // 关闭弹窗
    HiddenLoading(true);
    
    if (errMsg.isRefreshToken) {
      return Promise.resolve(req(err.config));
    } if (errMsg.failed && errMsg.failed.isOk && errMsg.isFetry) {
      message.loading({
        key: msg_key,
        content: `正在尝试第${ failedRetryCount - (errMsg.failed?.retryCount || 0) }次重试...`,
      });
      const res = await errMsg.failed.result();
      return Promise.resolve(res);
    }

    errMsg.isLoading ? message.open(errMsg.config) : null;
    return Promise.reject(err);
  },
);

const requestFailedRetry = (err: AxiosError<IAxiosResponseBody<any>>) => {
  let result: IRequestFailedRetry = {
    isOk: false,
    retryCount: err.config.failedRetryCount || 0,
    result: () => Promise.reject(err),
  };
  const config = err.config;
  if (config && config.failedRetryCount) {
    config.failedRetryCount--;
    config.__timestamp = Date.now();
    result.isOk = true;
    result.result = () => new Promise((resolve) => {
      setTimeout(() => {
        resolve(req(config))
      }, failedRetryIntervalTime);
    });
  } else {
    result = {
      isOk: false,
      retryCount: 0,
      result: () => Promise.reject(err),
    };
  }

  result.retryCount = config.failedRetryCount || 0;
  return result;
};

const formatParamsArray = (params: Record<string, any>) => {
  if (!params) {
    return {};
  }
  Object.keys(params).forEach((f) => {
    if (Object.prototype.toString.call(params[f]) === "[object Array]") {
      params[f] = JSON.stringify(params[f]);
    }
  });
  return params;
};

/**
 * Http Get Request
 * @param url
 * @param params
 * @returns
 */
const Get = <D = any>(url: string, params?: any, options?: AxiosRequestConfig) => req<D>({
  url,
  method: "GET",
  params: formatParamsArray(params),
  ...options,
});

/**
 * Http Post Request
 * @param url
 * @param data
 * @returns
 */
const Post = <D = any>(url: string, data?: any, options?: AxiosRequestConfig) => req<D>({
  url,
  method: "POST",
  data,
  ...options,
});

/**
 * Http Delete Request
 * @param url
 * @param data
 * @returns
 */
const Delete = <D = any>(url: string, params?: any, options?: AxiosRequestConfig) => req<D>({
  url,
  method: "DELETE",
  params: formatParamsArray(params),
  ...options,
});

/**
 * Http Put Request
 * @param url
 * @param data
 * @returns
 */
const Put = <D = any>(url: string, data?: any, options?: AxiosRequestConfig) => req<D>({
  url,
  method: "PUT",
  data,
  ...options,
});

/**
 * Http Request Hook
 * @returns
 */
export default {
  Get,
  Post,
  Put,
  Delete,
  Request: req,
};
